package lb

import (
	"bytes"
	"errors"
	"github.com/sirupsen/logrus"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"time"
)

const (
	defaultRetryCount  = 0
	defaultHTTPTimeout = 30 * time.Second
)

type Option struct {
	Timeout    time.Duration
	RetryCount int
}

type HttpClient struct {
	Client *http.Client
	Option Option
	apps   *Apps
}

func NewHttpClient(opts ...Option) *HttpClient {
	c := &HttpClient{}
	if len(opts) > 0 {
		c.Option = opts[0]
	} else {
		c.Option = Option{
			Timeout:    defaultHTTPTimeout,
			RetryCount: defaultRetryCount,
		}
	}
	c.Client = &http.Client{
		Timeout: c.Option.Timeout,
	}
	return c
}

var parseURL = url.Parse

func (c *HttpClient) NewRequest(method, url string, body io.Reader) (*http.Request, error) {
	if method == "" {
		method = "GET"
	}
	//
	u, err := parseURL(url) // Just url.Parse (url is shadowed for godoc).
	if err != nil {
		return nil, err
	}
	//重置请求host
	name := u.Host
	//name := u.Hostname()
	app := c.apps.Get(name)
	if app == nil {
		return nil, errors.New("无可用应用，应用名称: " + name + "，请求：" + url)
	}
	ins := app.Get(url)
	if ins == nil {
		return nil, errors.New("无可用实例，应用名称: " + name + "，请求：" + url)
	}
	u.Host = ins.Address

	url = u.String()
	r, err := http.NewRequest(method, url, body)
	return r, err
}

// Do makes an HTTP request with the native `http.Do` interface
func (c *HttpClient) Do(request *http.Request) (*http.Response, error) {
	return c.TryDo(c.Option.RetryCount, request)
}

func (c *HttpClient) TryDo(retries int, request *http.Request) (res *http.Response, err error) {
	for i := 0; i <= retries; i++ {
		res, err = c.do(request)
		if err != nil {
			continue
		}
		if res.StatusCode >= http.StatusInternalServerError {
			continue
		}
		return res, err
	}

	return res, err
}

// Do makes an HTTP request with the native `http.Do` interface
func (c *HttpClient) do(request *http.Request) (*http.Response, error) {
	//调用请求
	res, err := c.Client.Do(request)
	if err != nil {
		return nil, err
	}
	// 如果出错就不需要close，因此defer语句放在err处理逻辑后面
	// Clients must call resp.Body.Close when finished reading resp.Body -- from golang doc
	defer res.Body.Close()

	//处理response
	respBody, err := ioutil.ReadAll(res.Body)
	//Reset resp.Body so it can be use again
	res.Body = ioutil.NopCloser(bytes.NewBuffer(respBody))
	//

	if err := res.Body.Close(); err != nil {
		logrus.Error(err)
	}

	return res, err
}
